@fieldwangai/agentflow 0.1.70 → 0.1.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,10 @@ import {
7
7
  expandAgentflowHomePath,
8
8
  getAgentflowDataRoot,
9
9
  getAgentflowDataRootOverride,
10
+ getAgentflowSkillsRoot,
11
+ getAgentflowSkillsRootOverride,
10
12
  writeAgentflowDataRootOverride,
13
+ writeAgentflowSkillsRootOverride,
11
14
  } from "./paths.mjs";
12
15
 
13
16
  function defaultAgentflowDataRoot() {
@@ -28,6 +31,14 @@ function normalizeDataRoot(raw) {
28
31
  return dataRoot;
29
32
  }
30
33
 
34
+ function normalizeSkillsRoot(raw) {
35
+ const skillsRoot = expandAgentflowHomePath(raw);
36
+ if (skillsRoot && !path.isAbsolute(skillsRoot)) {
37
+ throw new Error("skillsRoot must be an absolute path");
38
+ }
39
+ return skillsRoot;
40
+ }
41
+
31
42
  function copyEntry(src, dest) {
32
43
  const stat = fs.lstatSync(src);
33
44
  if (stat.isSymbolicLink()) {
@@ -45,6 +56,12 @@ function copyEntry(src, dest) {
45
56
  fs.copyFileSync(src, dest);
46
57
  }
47
58
 
59
+ function copyEntryIfMissing(src, dest) {
60
+ if (fs.existsSync(dest)) return false;
61
+ copyEntry(src, dest);
62
+ return true;
63
+ }
64
+
48
65
  function migrateDataRoot(oldRoot, newRoot) {
49
66
  const from = path.resolve(oldRoot);
50
67
  const to = path.resolve(newRoot);
@@ -63,9 +80,49 @@ function migrateDataRoot(oldRoot, newRoot) {
63
80
  return { migrated: true, copied: true };
64
81
  }
65
82
 
83
+ function legacyCodexSkillsRoot() {
84
+ return path.join(os.homedir(), ".codex", "skills");
85
+ }
86
+
87
+ function migrateSkillsRoot(oldRoot, newRoot) {
88
+ const from = path.resolve(oldRoot);
89
+ const to = path.resolve(newRoot);
90
+ if (from === to) return { migrated: false, copied: false };
91
+ if (pathInside(from, to)) {
92
+ throw new Error("skillsRoot target cannot be inside the current skills root");
93
+ }
94
+ if (pathInside(to, from)) {
95
+ throw new Error("skillsRoot target cannot be a parent of the current skills root");
96
+ }
97
+ fs.mkdirSync(to, { recursive: true });
98
+ if (!fs.existsSync(from)) return { migrated: true, copied: false };
99
+ for (const name of fs.readdirSync(from)) {
100
+ copyEntry(path.join(from, name), path.join(to, name));
101
+ }
102
+ return { migrated: true, copied: true };
103
+ }
104
+
105
+ function migrateLegacyCodexSkills(targetRoot) {
106
+ const from = legacyCodexSkillsRoot();
107
+ const to = path.resolve(targetRoot);
108
+ const source = path.resolve(from);
109
+ if (source === to || !fs.existsSync(source)) return { migrated: false, copied: 0, source };
110
+ if (pathInside(source, to) || pathInside(to, source)) return { migrated: false, copied: 0, source };
111
+ fs.mkdirSync(to, { recursive: true });
112
+ let copied = 0;
113
+ for (const name of fs.readdirSync(source)) {
114
+ if (copyEntryIfMissing(path.join(source, name), path.join(to, name))) copied += 1;
115
+ }
116
+ return { migrated: copied > 0, copied, source };
117
+ }
118
+
66
119
  export function readAdminStorageConfig() {
67
120
  const envDataRoot = normalizeDataRoot(process.env.AGENTFLOW_HOME || "");
121
+ const envSkillsRoot = normalizeSkillsRoot(process.env.AGENTFLOW_SKILLS_ROOT || "");
68
122
  const configuredDataRoot = getAgentflowDataRootOverride();
123
+ const configuredSkillsRoot = getAgentflowSkillsRootOverride();
124
+ const skillsRoot = getAgentflowSkillsRoot();
125
+ const legacySkillsRoot = legacyCodexSkillsRoot();
69
126
  return {
70
127
  version: 1,
71
128
  dataRoot: getAgentflowDataRoot(),
@@ -73,21 +130,49 @@ export function readAdminStorageConfig() {
73
130
  defaultDataRoot: defaultAgentflowDataRoot(),
74
131
  envDataRoot,
75
132
  envLocked: Boolean(envDataRoot),
133
+ skillsRoot,
134
+ configuredSkillsRoot,
135
+ defaultSkillsRoot: path.join(getAgentflowDataRoot(), "skills"),
136
+ envSkillsRoot,
137
+ skillsEnvLocked: Boolean(envSkillsRoot),
138
+ legacySkillsRoot,
139
+ legacySkillsRootExists: fs.existsSync(legacySkillsRoot),
76
140
  configPath: AGENTFLOW_HOME_CONFIG_PATH,
77
141
  };
78
142
  }
79
143
 
80
144
  export function writeAdminStorageConfig(config = {}) {
81
145
  const current = readAdminStorageConfig();
82
- if (current.envLocked) {
83
- throw new Error("AGENTFLOW_HOME is set; update the environment variable instead of Admin Settings");
84
- }
85
146
  const nextRoot = normalizeDataRoot(config.dataRoot ?? "");
86
147
  if (!nextRoot) throw new Error("dataRoot is required");
87
- const migration = migrateDataRoot(current.dataRoot, nextRoot);
88
- writeAgentflowDataRootOverride(nextRoot);
148
+ let migration = { migrated: false, copied: false };
149
+ if (current.envLocked) {
150
+ if (path.resolve(nextRoot) !== path.resolve(current.dataRoot)) {
151
+ throw new Error("AGENTFLOW_HOME is set; update the environment variable instead of Admin Settings");
152
+ }
153
+ } else {
154
+ migration = migrateDataRoot(current.dataRoot, nextRoot);
155
+ writeAgentflowDataRootOverride(nextRoot);
156
+ }
157
+ const afterDataRoot = readAdminStorageConfig();
158
+ if (afterDataRoot.skillsEnvLocked) {
159
+ return {
160
+ ...afterDataRoot,
161
+ migration,
162
+ skillsMigration: { skipped: true, reason: "AGENTFLOW_SKILLS_ROOT is set" },
163
+ };
164
+ }
165
+ const nextSkillsRoot = normalizeSkillsRoot(config.skillsRoot ?? afterDataRoot.defaultSkillsRoot);
166
+ if (!nextSkillsRoot) throw new Error("skillsRoot is required");
167
+ const skillsMigration = migrateSkillsRoot(current.skillsRoot, nextSkillsRoot);
168
+ writeAgentflowSkillsRootOverride(nextSkillsRoot);
169
+ const legacySkillsMigration = config.migrateLegacySkills === false
170
+ ? { migrated: false, copied: 0, source: legacyCodexSkillsRoot(), skipped: true }
171
+ : migrateLegacyCodexSkills(nextSkillsRoot);
89
172
  return {
90
173
  ...readAdminStorageConfig(),
91
174
  migration,
175
+ skillsMigration,
176
+ legacySkillsMigration,
92
177
  };
93
178
  }
@@ -158,6 +158,7 @@ function normalizeFrontmatterSlots(arr) {
158
158
  else if (typeof def !== "string") def = String(def);
159
159
  const slot = { type, name, default: def };
160
160
  if (item.required != null) slot.required = Boolean(item.required);
161
+ if (item.description != null) slot.description = String(item.description);
161
162
  slot.showOnNode = item.showOnNode != null ? Boolean(item.showOnNode) : defaultShowOnNodeForSlot(slot);
162
163
  return slot;
163
164
  });
@@ -137,18 +137,26 @@ export function readComposerSkillDetail(packageRoot, workspaceRoot, keyOrName) {
137
137
  return registryReadSkillDetail(packageRoot, workspaceRoot, keyOrName);
138
138
  }
139
139
 
140
+ function skillNameFromKey(keyOrName) {
141
+ const value = String(keyOrName || "").trim();
142
+ if (!value) return "";
143
+ const idx = value.indexOf(":");
144
+ return idx >= 0 ? value.slice(idx + 1).trim() : value;
145
+ }
146
+
140
147
  export function loadResourcesForSkillKeys(skillKeys, packageRoot, workspaceRoot) {
141
148
  if (!Array.isArray(skillKeys) || skillKeys.length === 0) {
142
149
  return { skills: [], references: [], skillsHint: "", hasContext: false };
143
150
  }
144
151
  const wanted = new Set(skillKeys.map((x) => String(x || "").trim()).filter(Boolean));
145
152
  if (wanted.size === 0) return { skills: [], references: [], skillsHint: "", hasContext: false };
153
+ const wantedNames = new Set(Array.from(wanted).map(skillNameFromKey).filter(Boolean));
146
154
 
147
155
  const exactByKey = new Map(registryListSkills(packageRoot, workspaceRoot).map((item) => [item.key, item]));
148
156
  const candidateItems = [];
149
157
  const seenKeys = new Set();
150
158
  for (const item of registryListUniqueSkills(packageRoot, workspaceRoot)) {
151
- if (!wanted.has(item.key) && !wanted.has(item.name)) continue;
159
+ if (!wanted.has(item.key) && !wanted.has(item.name) && !wantedNames.has(item.name)) continue;
152
160
  candidateItems.push(item);
153
161
  seenKeys.add(item.key);
154
162
  }
@@ -289,6 +289,14 @@
289
289
  "displayName": "Create GitLab MR",
290
290
  "description": "Create or reuse a GitLab merge request for the current branch and output its URL"
291
291
  },
292
+ "tool_wecom_send_group_markdown": {
293
+ "displayName": "WeCom Group Markdown",
294
+ "description": "Send a Markdown message through a WeCom group robot webhook"
295
+ },
296
+ "tool_wecom_send_app_markdown": {
297
+ "displayName": "WeCom App Markdown",
298
+ "description": "Send a Markdown message to users through a WeCom enterprise application"
299
+ },
292
300
  "tool_nodejs": {
293
301
  "displayName": "Node.js Script",
294
302
  "description": "Execute Node.js script, success determined by exit code, stdout as result"
@@ -289,6 +289,14 @@
289
289
  "displayName": "提交 GitLab MR",
290
290
  "description": "为当前分支创建或复用 GitLab Merge Request,并输出 MR 链接"
291
291
  },
292
+ "tool_wecom_send_group_markdown": {
293
+ "displayName": "企业微信群 Markdown",
294
+ "description": "通过企业微信群机器人 webhook 发送 Markdown 消息"
295
+ },
296
+ "tool_wecom_send_app_markdown": {
297
+ "displayName": "企业微信应用 Markdown",
298
+ "description": "通过企业微信应用消息接口发送 Markdown 给用户"
299
+ },
292
300
  "tool_nodejs": {
293
301
  "displayName": "Node.js 脚本",
294
302
  "description": "执行 Node.js 脚本,以 exit code 判断成败,stdout 作为结果"
@@ -72,6 +72,7 @@ function normalizeSlotList(value) {
72
72
  default: def == null ? "" : String(def),
73
73
  };
74
74
  if (slot.required != null) normalized.required = Boolean(slot.required);
75
+ if (slot.description != null) normalized.description = String(slot.description);
75
76
  normalized.showOnNode = slot.showOnNode != null
76
77
  ? Boolean(slot.showOnNode)
77
78
  : Boolean(normalized.required) || type.toLowerCase() === "node";
package/bin/lib/paths.mjs CHANGED
@@ -49,6 +49,11 @@ export function getAgentflowDataRootOverride() {
49
49
  return expandAgentflowHomePath(config.dataRoot || "");
50
50
  }
51
51
 
52
+ export function getAgentflowSkillsRootOverride() {
53
+ const config = readAgentflowHomeConfig();
54
+ return expandAgentflowHomePath(config.skillsRoot || "");
55
+ }
56
+
52
57
  export function writeAgentflowDataRootOverride(dataRoot) {
53
58
  const nextRoot = expandAgentflowHomePath(dataRoot);
54
59
  if (nextRoot && !path.isAbsolute(nextRoot)) {
@@ -62,6 +67,19 @@ export function writeAgentflowDataRootOverride(dataRoot) {
62
67
  return nextRoot;
63
68
  }
64
69
 
70
+ export function writeAgentflowSkillsRootOverride(skillsRoot) {
71
+ const nextRoot = expandAgentflowHomePath(skillsRoot);
72
+ if (nextRoot && !path.isAbsolute(nextRoot)) {
73
+ throw new Error("skillsRoot must be an absolute path");
74
+ }
75
+ const current = readAgentflowHomeConfig();
76
+ const next = { ...current, skillsRoot: nextRoot };
77
+ if (!nextRoot) delete next.skillsRoot;
78
+ fs.mkdirSync(path.dirname(AGENTFLOW_HOME_CONFIG_PATH), { recursive: true });
79
+ fs.writeFileSync(AGENTFLOW_HOME_CONFIG_PATH, JSON.stringify(next, null, 2) + "\n", "utf-8");
80
+ return nextRoot;
81
+ }
82
+
65
83
  export function getAgentflowDataRoot() {
66
84
  const env = process.env.AGENTFLOW_HOME;
67
85
  if (env != null && String(env).trim() !== "") {
@@ -72,6 +90,16 @@ export function getAgentflowDataRoot() {
72
90
  return path.join(os.homedir(), "agentflow");
73
91
  }
74
92
 
93
+ export function getAgentflowSkillsRoot() {
94
+ const env = process.env.AGENTFLOW_SKILLS_ROOT;
95
+ if (env != null && String(env).trim() !== "") {
96
+ return expandAgentflowHomePath(env);
97
+ }
98
+ const configured = getAgentflowSkillsRootOverride();
99
+ if (configured) return configured;
100
+ return path.join(getAgentflowDataRoot(), "skills");
101
+ }
102
+
75
103
  export const AGENTFLOW_DEFAULT_USER_ID = "";
76
104
 
77
105
  export function sanitizeAgentflowUserId(userId) {
@@ -302,6 +330,8 @@ export const LOCAL_ONLY_DEFINITION_IDS = new Set([
302
330
  "tool_git_worktree_load",
303
331
  "tool_git_worktree_unload",
304
332
  "tool_gitlab_create_mr",
333
+ "tool_wecom_send_group_markdown",
334
+ "tool_wecom_send_app_markdown",
305
335
  "tool_print",
306
336
  "tool_user_check",
307
337
  "tool_user_ask",
@@ -2,6 +2,7 @@ import fs from "fs";
2
2
  import os from "os";
3
3
  import path from "path";
4
4
  import yaml from "js-yaml";
5
+ import { getAgentflowSkillsRoot } from "./paths.mjs";
5
6
 
6
7
  const fileCache = new Map();
7
8
  const CACHE_TTL_MS = 60_000;
@@ -9,10 +10,11 @@ const SOURCE_PRIORITY = new Map([
9
10
  ["workspace-agents", 100],
10
11
  ["workspace-codex", 95],
11
12
  ["workspace-cursor", 90],
13
+ ["agentflow-root", 85],
12
14
  ["builtin", 80],
13
- ["global-agents", 70],
14
- ["global-codex", 65],
15
- ["global-cursor", 60],
15
+ ["global-agents", 55],
16
+ ["global-codex", 50],
17
+ ["global-cursor", 45],
16
18
  ]);
17
19
 
18
20
  function readFileCached(absPath) {
@@ -28,6 +30,10 @@ function readFileCached(absPath) {
28
30
  }
29
31
  }
30
32
 
33
+ export function clearSkillRegistryCache() {
34
+ fileCache.clear();
35
+ }
36
+
31
37
  export function stripSkillFrontmatter(content) {
32
38
  const raw = String(content || "");
33
39
  const match = raw.match(/^---\s*\r?\n[\s\S]*?\r?\n---\s*\r?\n?/);
@@ -109,11 +115,17 @@ export function defaultSkillSources(packageRoot, workspaceRoot) {
109
115
  },
110
116
  ];
111
117
  if (workspaceRoot) sources.push(...workspaceSkillSources(workspaceRoot, "workspace", "工作区"));
118
+ sources.push({
119
+ source: "agentflow-root",
120
+ sourceLabel: "AgentFlow Skills",
121
+ dir: getAgentflowSkillsRoot(),
122
+ installedBy: "agentflow-admin",
123
+ });
112
124
  const home = os.homedir();
113
125
  sources.push(
114
- { source: "global-agents", sourceLabel: "全局 .agents", dir: path.join(home, ".agents", "skills"), installedBy: "global" },
115
- { source: "global-cursor", sourceLabel: "全局 .cursor", dir: path.join(home, ".cursor", "skills"), installedBy: "global" },
116
- { source: "global-codex", sourceLabel: "全局 .codex", dir: path.join(home, ".codex", "skills"), installedBy: "skillhub" },
126
+ { source: "global-agents", sourceLabel: "Legacy ~/.agents", dir: path.join(home, ".agents", "skills"), installedBy: "global" },
127
+ { source: "global-cursor", sourceLabel: "Legacy ~/.cursor", dir: path.join(home, ".cursor", "skills"), installedBy: "global" },
128
+ { source: "global-codex", sourceLabel: "Legacy ~/.codex", dir: path.join(home, ".codex", "skills"), installedBy: "skillhub-legacy" },
117
129
  );
118
130
  return sources;
119
131
  }
@@ -183,9 +195,10 @@ export function listUniqueSkills(packageRoot, workspaceRoot, opts = {}) {
183
195
  export function readSkillDetail(packageRoot, workspaceRoot, keyOrName) {
184
196
  const wanted = String(keyOrName || "").trim();
185
197
  if (!wanted) return null;
198
+ const wantedName = wanted.includes(":") ? wanted.slice(wanted.indexOf(":") + 1).trim() : wanted;
186
199
  const all = listSkills(packageRoot, workspaceRoot);
187
200
  const item = all.find((skill) => skill.key === wanted)
188
- || dedupeSkillsByName(all).find((skill) => skill.name === wanted);
201
+ || dedupeSkillsByName(all).find((skill) => skill.name === wanted || skill.name === wantedName);
189
202
  if (!item) return null;
190
203
  return item;
191
204
  }